package featuredb

import (
	"sort"
	"time"

	"gitee.com/bjf-fhe/apicat/entry/etype"
	"github.com/boltdb/bolt"
	"google.golang.org/protobuf/proto"
)

const FingerPrintUnit = 50
const IntervalBetweenSlowRequest = 3000

//获得该IP的参数并发访问指纹
func (ip *IpStatForOp) GetFingerprint() int64 {
	timestampes := make([][2]int64, 0)
	for k, v := range ip.StatPerVars {
		timestampes = append(timestampes, [2]int64{k, v})
	}
	sort.Slice(timestampes, func(i, j int) bool {
		return timestampes[i][1] < timestampes[j][1]
	})
	var longInterval int64
	var maxLongInterval int64
	var lastTimestamp int64
	var lastK int

	for k, v := range timestampes {

		if v[1]-lastTimestamp < IntervalBetweenSlowRequest {
			lastK = k
			longInterval++
		} else {
			if longInterval > 0 && longInterval > maxLongInterval {
				maxLongInterval = longInterval
			}
			longInterval = 0
		}
		lastTimestamp = v[1]
	}

	for k := 0; k < lastK; k++ {
		delete(ip.StatPerVars, timestampes[k][0])
	}
	return (maxLongInterval / FingerPrintUnit)
}

//每个path-method一个统计信息
type StatWrapper struct {
	key  []byte
	stat *Stat
	db   *DB
	// inMemoryHotIPStat IPStatPool
}

type judgement struct {
}

func (j judgement) GetErrorType() etype.EntryError {
	return etype.Illegal_DiffusedArgs
}

func (d *StatWrapper) JudgeRequest(ip string, at time.Time, vars map[string]string) Judgement {
	var key = d.db.VarsToKey(vars)
	var ipKey = d.db.IPToKey(ip)

	var judge *judgement

	stat, ok := d.stat.StatOfSingleIP[ipKey]

	if !ok {
		stat = &IpStatForOp{
			StatPerVars: make(map[int64]int64),
		}
		if d.stat.StatOfSingleIP == nil {
			d.stat.StatOfSingleIP = make(map[int64]*IpStatForOp)
		}
		d.stat.StatOfSingleIP[ipKey] = stat
	}

	//应该使用logentry的时间戳，而不是当前时间戳
	stat.StatPerVars[key] = at.Unix()

	var fp = stat.GetFingerprint()

	if d.IsStealingIP(fp) {
		judge = &judgement{}
	}

	statOfVar, ok := d.stat.StatPerVars[key]
	if !ok {
		statOfVar = newVarStat()
		d.stat.StatPerVars[key] = statOfVar
	}
	//仅对未被判定为错误的访问请求进行入库更新
	if judge == nil {
		//先完成新的数据的写入
		var statOfIP = statOfVar.StatPerIPs[ipKey]
		d.stat.SizeOfIPs[ipKey] = fp
		statOfVar.StatPerIPs[ipKey] = statOfIP + 1
		//再进行状态更新
		if changed := statOfVar.reJudge(d.stat); changed {
			if statOfVar.PredictedType == UrlType_UrlNormal {
				if d.stat.PredictedType == UrlType_UrlUnspecified {
					d.stat.PredictedType = UrlType_UrlNormal
				} else {
					//TODO 应考虑d.stat.PredictedType == UrlType_UrlRestricted等的情况
				}
			} else if IsSimilar(len(d.stat.StatPerVars), len(d.stat.SizeOfIPs)) {
				d.stat.PredictedType = UrlType_UrlForSpecifiedUser
			} else {
				d.stat.PredictedType = UrlType_UrlRestricted
			}
		}
		//最后保存
		d.Save()
		return nil
	} else {
		return judge
	}
}

//判断是否是盗用IP，判断目前这个指纹
func (d *StatWrapper) IsStealingIP(fp int64) bool {
	var currentMap []int64
	for _, i := range d.stat.SizeOfIPs {
		for len(currentMap) <= int(i) {
			currentMap = append(currentMap, 0)
		}
		currentMap[i]++
	}
	//判断依据：当前指纹远大于已存在的最大指纹的3/2
	return fp > int64(len(currentMap))*3/2
}

//对参数请求的类型进行判定
func (d *VarStat) reJudge(statOfUrl *Stat) bool {
	var oldType = d.PredictedType
	var newType = oldType
	if FarSmaller(len(d.StatPerIPs), len(statOfUrl.SizeOfIPs)) {
		newType = UrlType_UrlRestricted
	} else if IsSimilar(len(d.StatPerIPs), len(statOfUrl.SizeOfIPs)) {
		newType = UrlType_UrlNormal
	}
	return oldType != newType
}

//判断两个整数是否大致相等
func IsSimilar(a, b int) bool {
	return a > b*9/10 && a < b*11/10
}

//判断a是否比b的1/3还要小
func FarSmaller(a, b int) bool {
	return a < b/3
}

func (d *StatWrapper) Save() error {
	return d.db.low.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket(FeatureDBStatTable)
		var bts []byte
		bts, err := proto.Marshal(d.stat)
		if err == nil {
			err = bucket.Put(d.key, bts)
		}
		return err
	})
}

func newVarStat() *VarStat {
	return &VarStat{
		StatPerIPs: make(map[int64]int64),
	}
}
